﻿#include "curvesPlot.h"
#include <QtWidgets/QColorDialog>
#include <QtCore/QDebug>

CurvesPlot::CurvesPlot(QWidget *parent, int curvesCnt) :
	QCustomPlot(parent),
	_graphRecord(curvesCnt),
	displayUpdate(true),
	xAxisRange(DEFAULT_RANGE),
	autoScroll(true),
	tracerEnable(false),
	lastPoint(new QPointF(0, 0)),
	rb(new QRubberBand(QRubberBand::Rectangle, this)),
	startPos(0, 0),
	basicKey(0),
	traceIndex(0),
	anchoringX(0) {
	this->setInteractions(QCP::iRangeDrag //可平移
		| QCP::iRangeZoom //可滚轮缩放
		//| QCP::iSelectPlottables //可选中曲线
		//| QCP::iSelectLegend
		);//可选中图例
	//禁用抗锯齿，以提高性能
	this->setNoAntialiasingOnDrag(true);
	//选中改变的事件
	connect(this, SIGNAL(selectionChangedByUser()), this, SLOT(when_selectionChangedByUser()));
	//双击改变图例的事件
	connect(this, SIGNAL(legendDoubleClick(QCPLegend*, QCPAbstractLegendItem*, QMouseEvent*)), this, SLOT(when_legendDoubleClick(QCPLegend*, QCPAbstractLegendItem*, QMouseEvent*)));
	
	// 设置背景颜色
	QLinearGradient plotGradient;
	plotGradient.setStart(0, 0);
	plotGradient.setFinalStop(0, 350);
	plotGradient.setColorAt(0, QColor(80, 80, 80));
	plotGradient.setColorAt(1, QColor(50, 50, 50));
	this->setBackground(plotGradient);      
	// 设置QCPAxisRect背景颜色
	QLinearGradient axisRectGradient;
	axisRectGradient.setStart(0, 0);
	axisRectGradient.setFinalStop(0, 350);
	axisRectGradient.setColorAt(0, QColor(80, 80, 80));
	axisRectGradient.setColorAt(1, QColor(50, 50, 50));
	this->axisRect()->setBackground(axisRectGradient);   

	//扩展左侧、右侧和底部的空白区域
	connect(this->yAxis, SIGNAL(rangeChanged(QCPRange)), this->yAxis2, SLOT(setRange(QCPRange)));
	this->yAxis2->setVisible(true);
	connect(this->xAxis, SIGNAL(rangeChanged(QCPRange)), this->xAxis2, SLOT(setRange(QCPRange)));
	this->xAxis2->setVisible(true);
	this->axisRect()->axis(QCPAxis::atRight, 0)->setPadding(30);
	this->axisRect()->axis(QCPAxis::atBottom, 0)->setPadding(30);

	// 设置QCPAxis轴的风格
	this->xAxis->setBasePen(QPen(Qt::white, 1));  // 轴线的画笔
	this->xAxis->setTickPen(QPen(Qt::white, 1));  // 轴刻度线的画笔
	this->xAxis->setSubTickPen(QPen(Qt::white, 1)); // 轴子刻度线的画笔
	this->xAxis->setTickLabelColor(Qt::white);  // 轴刻度文字颜色
	this->xAxis->setTickLengthIn(3);       // 轴线内刻度的长度

	this->xAxis2->setBasePen(QPen(Qt::white, 1));  // 轴线的画笔
	this->xAxis2->setTickPen(QPen(Qt::white, 1));  // 轴刻度线的画笔
	this->xAxis2->setSubTickPen(QPen(Qt::white, 1)); // 轴子刻度线的画笔
	this->xAxis2->setTickLabelColor(Qt::white);  // 轴刻度文字颜色
	this->xAxis2->setTickLengthIn(3);       // 轴线内刻度的长度

	this->yAxis->setBasePen(QPen(Qt::white, 1));  // 轴线的画笔
	this->yAxis->setTickPen(QPen(Qt::white, 1));  // 轴刻度线的画笔
	this->yAxis->setSubTickPen(QPen(Qt::white, 1)); // 轴子刻度线的画笔
	this->yAxis->setTickLabelColor(Qt::white);  // 轴刻度文字颜色
	this->yAxis->setTickLengthIn(3);       // 轴线内刻度的长度

	this->yAxis2->setBasePen(QPen(Qt::white, 1));  // 轴线的画笔
	this->yAxis2->setTickPen(QPen(Qt::white, 1));  // 轴刻度线的画笔
	this->yAxis2->setSubTickPen(QPen(Qt::white, 1)); // 轴子刻度线的画笔
	this->yAxis2->setTickLabelColor(Qt::white);  // 轴刻度文字颜色
	this->yAxis2->setTickLengthIn(3);       // 轴线内刻度的长度
	// 每条网格对应一个刻度
	this->xAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine));     // 网格线(对应刻度)画笔
	this->yAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine));
	this->xAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); // 子网格线(对应子刻度)画笔
	this->yAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine));
	this->xAxis->grid()->setSubGridVisible(true);     // 显示子网格线
	this->yAxis->grid()->setSubGridVisible(true);

	//设置默认区间
	this->xAxis->setRange(0, DEFAULT_RANGE);
	////设置主刻度数量
	//this->xAxis->ticker()->setTickCount(11);
	//this->yAxis->ticker()->setTickCount(11);

	/*显示数值的提示框*/
	presLabel = new QCPItemText(this);
	presLabel->setPositionAlignment(Qt::AlignVCenter | Qt::AlignLeft);
	presLabel->setText("");
	presLabel->setFont(QFont(font().family(), 8)); // make font a bit larger
	presLabel->setPen(QPen(Qt::white)); // show black border around text
	presLabel->setPadding(QMargins(2, 2, 2, 2));//边界宽度
	presLabel->setBrush(Qt::lightGray);
	presLabel->setVisible(false);

	//单击时指向数值的箭头:
	presArrow = new QCPItemLine(this);
	presArrow->start->setParentAnchor(presArrow->end);
	presArrow->start->setCoords(30, 0);//偏移量
	presLabel->position->setParentAnchor(presArrow->start);
	presArrow->setPen(QPen(Qt::white));
	presArrow->setHead(QCPLineEnding::esSpikeArrow);
	presArrow->setVisible(false);

	//游标以及游标文本框设置
	axisTracer = new AxisTag(this->xAxis, this->yAxis2);
	//鼠标跟随打开
	this->setMouseTracking(true);

	/*两点差值的显示框*/
	diffText = new QCPItemText(this);
	diffText->setPositionAlignment(Qt::AlignTop | Qt::AlignRight);
	diffText->setTextAlignment(Qt::AlignTop | Qt::AlignLeft);//文本框的原点位置
	diffText->position->setType(QCPItemPosition::ptAxisRectRatio);//位置类型（当前轴范围的比例）
	diffText->position->setCoords(0.98, 0); // place position at center/top of axis rect
	diffText->setText("dt=0\r\ndy=0");
	diffText->setFont(QFont(font().family(), 8)); // make font a bit larger
	diffText->setPen(QPen(Qt::black)); // show black border around text
	diffText->setPadding(QMargins(2, 2, 2, 2));//边界宽度
	diffText->setBrush(Qt::lightGray);
	diffText->setVisible(false);

	//右键菜单
	this->setContextMenuPolicy(Qt::ActionsContextMenu);
	//菜单-按钮1
	actshowAllGraph = new QAction("全显");
	connect(actshowAllGraph, SIGNAL(triggered(bool)), this, SLOT(showAllGraph()));
	this->addAction(actshowAllGraph);
	//菜单-按钮2
	actClearDatas = new QAction("清空历史数据");
	connect(actClearDatas, SIGNAL(triggered(bool)), this, SLOT(clearAllData()));
	this->addAction(actClearDatas);

	//初始化数据源
	_cache = new CurveCache(curvesCnt);

	//定频刷新曲线图
	startTimer(20, Qt::PreciseTimer);

	//重置基础key
	reloadBasicKey();
}

CurvesPlot::~CurvesPlot() {
	delete _cache;
}

void CurvesPlot::reloadBasicKey() {
	reloadKey = true;
	for (int i = 0; i < _graphRecord.count(); i++) {
		_graphRecord[i].reloadKey = true;
	}
}

void CurvesPlot::reloadGraphKey(uint8_t index) {
	_graphRecord[index].reloadKey = true;
}

//x轴的动态范围设置
void CurvesPlot::setXAxisDynamic(double range, double x) {
	double beginSeclf;
	double finishSeclf;
	if (x < range) {
		beginSeclf = 0;
		finishSeclf = range;
	}
	else {
		beginSeclf = x - range;
		finishSeclf = x;
	}
	this->xAxis->setRange(beginSeclf, finishSeclf);
	emit autoRangeAdjusted(this->xAxis->range());
}

//设置x轴的最大显示范围
void CurvesPlot::setXAxisRange(double range) {
	//清除显示范围历史记录
	xRangeList.clear();
	yRangeList.clear();
	xAxisRange = qMin<double>(range, 100000);
	setXAxisDynamic(xAxisRange, stopBasicKey);
	//此命令为刷新图形界面
	this->replot(QCustomPlot::rpQueuedReplot);
}

//设置游标跟踪的曲线
void CurvesPlot::setTraceGraph(int index) {
	traceIndex = index;
	if (this->graphCount() > 0 && curveIdxTographPtr.contains(index)) {		
		traceGraph = curveIdxTographPtr[index];
		tracerValid = true;
	}
	else {
		traceGraph = NULL;
		tracerValid = false;
		axisTracer->setGraph(nullptr);
	}
}

//修改波形
void CurvesPlot::modifyCurves() {
	//必须在显示打开的情况下才进行图层清空
	this->clearGraphs();
	//清除曲线编号映射
	curveIdxTographPtr.clear();
	graphPtrTocurveIdx.clear();

	this->yMax = 0;
	this->yMin = 0;
	//图层下标
	int graphIdx = 0;
	//名称下标
	int curveIdx = 0;
	for (QVector<GraphData>::iterator it = _graphRecord.begin(); it != _graphRecord.end(); it++) {
		bool check = it->isShow;
		if (check) {
			if (curveIdx > _graphRecord.size()) {
				qDebug() << QString("warning: MultiCurvesPlot::showCurves->超出数据源max index").arg(curveIdx);
				continue;
			}
			this->addGraph(this->xAxis, this->yAxis);
			QCPGraph* pGraph = graph(graphIdx);
			curveIdxTographPtr[curveIdx] = pGraph;//记录：曲线索引->graph指针的映射
			graphPtrTocurveIdx[pGraph] = curveIdx;//记录：graph指针->曲线索引的映射

			it->yMax = *std::max_element(std::begin(it->valVec), std::end(it->valVec));
			this->yMax = qMax<double>(it->yMax, this->yMax);
			it->yMin = *std::min_element(std::begin(it->valVec), std::end(it->valVec));
			this->yMin = qMin<double>(it->yMin, this->yMin);
			pGraph->setData(it->keyVec, it->valVec, true);//数据源 （todo:*it的合法性）
			QPen pen;
			pen.setWidth(1);//设置线的宽度,设置2会卡
			pen.setColor(it->color);//设置线的颜色
			pGraph->setPen(pen);
			pGraph->setLineStyle(QCPGraph::lsLine);//直线样式       
			pGraph->setName(it->name);//设置曲线名称
			graphIdx++;
			//确认当前设置是否包含游标对象
			if(curveIdx == traceIndex) {
				traceGraph = curveIdxTographPtr[curveIdx];
				tracerValid = true;
				axisTracer->setGraph(traceGraph);
			}
		}
		else {
			it->isShow = false;
			//确认当前设置是否包含游标对象
			if (curveIdx == traceIndex) {
				traceGraph = nullptr;
				tracerValid = false;
				axisTracer->setGraph(nullptr);
			}
		}
		curveIdx++;
	}
	//重新设置y轴范围
	this->yAxis->setRange(this->yMin *1.1, this->yMax * 1.1);
	this->replot();
}

//增加新的曲线点缓存
void CurvesPlot::addNewPointCache(GraphData *graphData, double key, double value) {
	//如果key大于keyVec中的最后一个key则直接添加，此方式确保了vector中的顺序一定是从小到大的（经过排序的）
	if (key > graphData->keyVec.last()) {
		graphData->keyVec.append(key);
		graphData->valVec.append(value);
	}
	else {
		//找到不大于key的首个iterator
		QVector<double>::iterator iter = qLowerBound(graphData->keyVec.begin(), graphData->keyVec.end(), key);
		int index = iter - graphData->keyVec.begin();
		//如果key找到一个相同的key和相对的value进行替代
		if (*iter == key) {
			//替换
			*iter = key;
			graphData->valVec[index] = value;
		}
		//如果key与*iter不相同则插入到*iter之前
		else {
			//在iterator之前插入key和value
			graphData->keyVec.insert(iter, key);
			graphData->valVec.insert(graphData->valVec.begin() + index, key);
		}	
	}
}

//数据源更新
void CurvesPlot::addData(SensorCache cache) {
	uint8_t index = cache.getIndex();
	uint8_t length = cache.getLength();
	uint16_t cntr = cache.getCntr();
	double key = 0;
	double value = cache.getValue();

	//检查下标是否正常
	if (index > _graphRecord.size()) {
		qDebug() << QString("warning:下位机企图绘制编号为%1的曲线！").arg(index);
		return;
	}
	//接收到的异常值直接不绘制
	if (qAbs<double>(value) > 1e12) {
		qDebug() << QString("warning:%1曲线数值超出范围！").arg(index);
		return;
	}

	//如果之前没有这条曲线，则添加曲线
	if (displayUpdate) {
		//检查是否需要重置basicKey或增加basicKey
		if (reloadKey) {
			basicKey++;
			reloadKey = false;
		}
		if (!reloadKey) {
			uint16_t correctKey = cntr - lastCntr;
			if (correctKey <= 1000) {
				basicKey += correctKey;
				//if (correctKey > 100) {
				//	qDebug() << "add! :" << index << basicKey << lastCntr << _graphRecord[index].lastCNTR << cntr;
				//}
			}
		}
		if (cntr != lastCntr) {
			lastCntr = cntr;
		}

		//确定当前时间戳上该曲线的key坐标
		if (_graphRecord[index].reloadKey) {
			key = basicKey;
			_graphRecord[index].reloadKey = false;
		}
		else {
			uint16_t correctKey = cntr - _graphRecord[index].lastCNTR;
			if (correctKey <= 1000) {
				if (cntr >= _graphRecord[index].lastCNTR) {
					key = basicKey + correctKey;
				}
				else {
					key = basicKey - correctKey;
				}
			}
			else {
				key = basicKey;
			}
		}

		//备份数据源
		//_graphRecord[index].keyVec.append(key);
		//_graphRecord[index].valVec.append(value);

		//增加新的曲线点缓存
		addNewPointCache(&_graphRecord[index], key, value);
		_graphRecord[index].valueLength = length;
		_graphRecord[index].lastCNTR = cntr;

		//第idx个曲线正在显示中
		if (curveIdxTographPtr.contains(index)) {
			QCPGraph* pGraph = curveIdxTographPtr[index];
			_graphRecord[index].yMax = qMax<double>(value, _graphRecord[index].yMax);
			this->yMax = qMax<double>(_graphRecord[index].yMax, this->yMax);
			_graphRecord[index].yMin = qMin<double>(value, _graphRecord[index].yMin);
			this->yMin = qMin<double>(_graphRecord[index].yMin, this->yMin);
			pGraph->addData(key, value);
		}
	}
}

//显示默认范围
void CurvesPlot::showAllGraph() {
	//清除显示范围历史记录
	xRangeList.clear();
	yRangeList.clear();
	//设置y轴范围
	this->yAxis->setRange(this->yMin * 1.1, this->yMax * 1.1);
	setXAxisDynamic(xAxisRange, stopBasicKey);
	//此命令为刷新图形界面
	this->replot(QCustomPlot::rpQueuedReplot);
}

//清除显示部分及缓存
void CurvesPlot::clearAllData() {
	//基础x归零（测试）
	basicKey = 0;
	//游标归零
	axisTracer->setGraph(nullptr);
	//清除显示部分
	for (int i = 0; i < graphCount(); i++) {
		graph(i)->data().data()->clear();
	}
	this->yMax = 0;
	this->yMin = 0;
	//清除缓存区
	for (int idx = 0; idx < _graphRecord.size(); idx++) {
		_graphRecord[idx].keyVec.clear();
		_graphRecord[idx].valVec.clear();
		_graphRecord[idx].yMax = 0;
		_graphRecord[idx].yMin = 0;
		_graphRecord[idx].valueLength = 0;
	}
}

//定时器触发曲线图形更新
void CurvesPlot::timerEvent(QTimerEvent *event) {
	Q_UNUSED(event);
	static double lastYMax;
	static double lastYMin;
	//是否显示波形
	if (displayUpdate) {
		if (lastYMax != this->yMax || lastYMin != this->yMin)
		{
			//设置y轴范围
			this->yAxis->setRange(this->yMin * 1.1, this->yMax * 1.1);
		}
		//x轴是否跟随
		if (autoScroll) {
			//跟踪显示时对暂停时的X值进行更新
			stopBasicKey = basicKey;
			setXAxisDynamic(this->xAxis->range().size(), basicKey);
		}
		else {
			emit autoRangeAdjusted(this->xAxis->range());
		}
	}
	//此命令为刷新图形界面
	this->replot(QCustomPlot::rpQueuedReplot);
	lastYMax = this->yMax;
	lastYMin = this->yMin;
	//游标更新
	if (tracerEnable) {
		//游标对饮的曲线有效且当前曲线的数据量不为空
		if (tracerValid && traceGraph->data().data()->size() != 0) {
			double x = xAxis->pixelToCoord(anchoringX);
			axisTracer->updatePosition(traceGraph, x);
		}
		else {
			axisTracer->setText("none...", "none...");
		}
	}
}

//保存轴系比例，和恢复轴系比例搭配使用
void CurvesPlot::storeAxisScope() {
	xRangeList.append(this->xAxis->range());
	yRangeList.append(this->yAxis->range());
}

//恢复轴系比例
void CurvesPlot::resumeAxisScope() {
	if (xRangeList.count() > 0 && yRangeList.count() > 0) {
		this->xAxis->setRange(xRangeList.takeLast());
		this->yAxis->setRange(yRangeList.takeLast());
	}
}

/*用户点选了图中的元素时：同步选中曲线和曲线图例*/
void CurvesPlot::when_selectionChangedByUser() {
	for (int i = 0; i < this->graphCount(); ++i) {
		QCPGraph *graph = this->graph(i);

		QCPPlottableLegendItem *item = this->legend->itemWithPlottable(graph);
		//选中了哪条曲线或者曲线的图例
		if (item->selected() || graph->selected()) {
			//同时选中曲线和图例
			item->setSelected(true);
			graph->setSelection(QCPDataSelection(graph->data()->dataRange()));
			traceGraph = graph;
		}
	}
}

/*双击图例修改曲线颜色*/
void CurvesPlot::when_legendDoubleClick(QCPLegend *legend, QCPAbstractLegendItem *legendItem, QMouseEvent *event) {
	Q_UNUSED(legend);
	Q_UNUSED(event);

	for (int i = 0; i < this->graphCount(); ++i) {
		QCPGraph *pGraph = this->graph(i);
		//查询被点击的图例项是哪个graph的图例项
		if (legendItem == this->legend->itemWithPlottable(pGraph)) {
			//映射表中必须存在该曲线
			if (graphPtrTocurveIdx.contains(pGraph)) {
				int curveIdx = graphPtrTocurveIdx[pGraph];//被选中的曲线编号
				QColor newColor = QColorDialog::getColor(_graphRecord[curveIdx].color, //默认颜色
					this,//父窗体
					"选择新颜色");
				//用户点击了确定
				if (newColor.isValid()) {
					_graphRecord[curveIdx].color = newColor;
					curveIdxTographPtr[curveIdx]->setPen(QColor(_graphRecord[curveIdx].color));
				}
			}
			this->deselectAll();//取消所有的点选
			break;
		}
	}
}

/*1、按下时，显示按下点的数值，松手时隐藏 2、计算游标值，并emit*/
void CurvesPlot::mousePressEvent(QMouseEvent *event) {
	QCustomPlot::mousePressEvent(event);

	if (event->buttons() & Qt::LeftButton) {
		presLabel->setVisible(true);
		presArrow->setVisible(true);
		double x = xAxis->pixelToCoord(event->pos().x());//像素坐标转plot坐标
		double y = yAxis->pixelToCoord(event->pos().y());
		presArrow->end->setCoords(x, y); // 箭头的终点
		presLabel->setText(QString("x = %1\ny=%2").arg(x).arg(y));//箭头框显示点击值
		solveDifference(QPointF(x, y));

		//放大缩小记录起始点
		startPos = event->pos();
		cancelRb = false;
		rb->resize(0, 0);
		rb->show();
	}
}

//鼠标松开时隐藏
void CurvesPlot::mouseReleaseEvent(QMouseEvent *event) {
	QCustomPlot::mouseReleaseEvent(event);

	if (event->button() == Qt::LeftButton) {
		presLabel->setVisible(false);
		presArrow->setVisible(false);

		//放大缩小执行
		rb->hide();
		QPoint delta_point = event->pos() - startPos;
		if (!cancelRb
			&& qAbs(delta_point.x()) > 10	//移动距离太短的话(<=10像素)，不操作
			&& qAbs(delta_point.y()) > 10) {
			if (delta_point.x() > 0) {
				//保存历史范围
				storeAxisScope();

				QRect normalRect = QRect(startPos, event->pos()).normalized();//任意两点定义矩形
				rb->setGeometry(normalRect);
				//if(xAxis->range().upper - xAxis->range().lower > 0.001)//x轴的显示范围<1ms时则不再放大
				this->xAxis->setRange(xAxis->pixelToCoord(normalRect.left()), xAxis->pixelToCoord(normalRect.right()));
				this->yAxis->setRange(yAxis->pixelToCoord(normalRect.bottom()), yAxis->pixelToCoord(normalRect.top()));
				autoScroll = false;
				emit autoRangeAdjusted(this->xAxis->range());
				this->replot();
			}
			else if (delta_point.x() < 0) {
				//恢复历史范围
				resumeAxisScope();
				this->replot();
			}
		}
	}
}

/*实时显示游标 关联：this->setmouseTracking(true)*/
void CurvesPlot::mouseMoveEvent(QMouseEvent *event) {
	QCustomPlot::mouseMoveEvent(event);
	//左键放大或缩小视界
	if (event->buttons() & Qt::LeftButton) {
		QRect normalRect = QRect(startPos, event->pos()).normalized();//任意两点定义矩形
		rb->setGeometry(normalRect);
	}
	//鼠标中键拖动视界
	else if (event->buttons() & Qt::MiddleButton) {
		//如果手动拖动的区域超过了当前X坐标值，则开启自动跟随
		if (this->xAxis->range().upper >= this->basicKey) {
			autoScroll = true;
		}
		//否则取消自动跟随
		else { 
			autoScroll = false;
		}
		middleMove = true;
	}
	//更新鼠标锚点
	if (tracerEnable) {
		anchoringX = event->pos().x();
	}
}

//是否开启差值计算显示
void CurvesPlot::setDiffSolveEnable(bool enable) {
	diffText->setVisible(enable);
}

/*求差，并更新到显示框*/
void CurvesPlot::solveDifference(QPointF newPoint) {
	diffText->setText(QString("dt=%1\r\ndy=%2")
		.arg(newPoint.x() - lastPoint->x())
		.arg(newPoint.y() - lastPoint->y()));
	*lastPoint = newPoint;
}

//是否启用游标
void CurvesPlot::setTracerEnable(bool enable) {
	//备份
	tracerEnable = enable;
	axisTracer->setVisible(tracerEnable);
}

//设置是否显示散点
void CurvesPlot::setScatterPointEnable(bool enable) {
	for (int i = 0; i < this->graphCount(); ++i) {
		if (enable)
			this->graph(i)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssDisc, 5));//显示散点
		else
			this->graph(i)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssNone));//不显示散点
	}
}

